/*
 * @Author: czy
 * @Date: 2021-04-04 22:39:40
 * @LastEditTime: 2021-07-09 12:28:33
 */
#include "../lib/io.h"
#include "../lib/vga.h"
#include "../lib/output.h"

static struct
{
    int16 *head;   // Head pointer.
    int16 *tail;   // Tail pointer.
    int16 *cursor; // Cursor pointer.
    int rows;      // Number of rows.
    int columns;   // Number of columns.
    int color;
    int status;
    int mode;
} vga = {(int16 *)vga_base, (int16 *)vga_base + ROW *COL, (int16 *)vga_base, ROW, COL, 0x07, 0, 0};

// Set cursor
static void reset_cursor(void)
{
    int num = vga.cursor - vga.head;
    int col_num, row_num;
    col_num = num % 256;
    row_num = num / 256;
    outb(index_port, col_port);
    outb(data_port, col_num);
    outb(index_port, row_port);
    outb(data_port, row_num);
}

// The screen scrolls down one line.
static void scrolling(void)
{
    if (vga.mode == 0 && vga.cursor >= vga.tail - COL)
    {
        int16 *p = vga.head;
        int16 temp;
        for (; p < vga.tail - COL - vga.columns; p++)
        {
            temp = *(p + vga.columns);
            *p = temp;
        }
        for (int i = 0; i < vga.columns; i++)
        {
            *p = (int16)0x0700;
            p++;
        }
        vga.cursor = vga.tail - COL - vga.columns;
    }
}

// Control character: \n
static void newline(void)
{
    vga.cursor = vga.head + (((vga.cursor - vga.head) / NEWLINE) + 1) * NEWLINE;
}

// Control character: \t
static void tab(void)
{
    vga.cursor = vga.head + (((vga.cursor - vga.head) / TAB) + 1) * TAB;
}

// Control character: \b
static void backspace(void)
{
    vga.cursor--;
}

// Control character: DEL
static void delete (void)
{
    backspace();
    *vga.cursor = (int16)(0x07 << 8 | ' ');
}

// Control character: \r
static void carriage(void)
{
    vga.cursor = vga.head + ((vga.cursor - vga.head) / NEWLINE) * NEWLINE;
}

// ESC
static void esc(void)
{
    vga.status = 1;
}

// Get raw color.
// 0: black; 1: red; 2: green; 3: yellow; 4: blue; 5: violet; 6: light blue; 7: white
static int get_raw_color(int color)
{
    switch (color)
    {
    case 0:
    case 2:
    case 5:
    case 7:
        return color;
    case 1:
        return 0x04;
    case 3:
        return 0x06;
    case 4:
        return 0x01;
    case 6:
        return 0x03;
    default:
        return 0x00;
    }
}

// Common characters
static void put_character(char ch)
{
    switch (vga.status)
    {
    case 0:
        *(vga.cursor) = (int16)(vga.color << 8 | ch);
        vga.cursor++;
        break;
    case 1:
        if (ch == '[')
        {
            vga.status = 2;
        }
        else
        {
            *(vga.cursor) = (int16)(vga.color << 8 | '[');
            vga.cursor++;
            vga.status = 0;
        }
        break;
    case 2:
        switch (ch)
        {
        case '0':
            vga.color = 0x07;
            break;
        case '1':
            vga.color = vga.color | 0x88;
            break;
        case '3':
            vga.status = 3;
            break;
        case '4':
            vga.status = 4;
            break;
        default:
            break;
        }
        break;
    case 3:
        if (ch == ';')
        {
            vga.status = 2;
        }
        else if (ch == 'm')
        {
            vga.status = 0;
        }
        else
        {
            vga.color = vga.color & 0xf0;
            vga.color = vga.color | get_raw_color(ch - '0');
        }
        break;
    case 4:
        if (ch == ';')
        {
            vga.status = 2;
        }
        else if (ch == 'm')
        {
            vga.status = 0;
        }
        else
        {
            vga.color = vga.color & 0x8f;
            vga.color = vga.color | (get_raw_color(ch - '0') << 4);
        }
        break;
    }
    if (ch == 'm')
    {
        vga.status = 0;
    }
}

/**
 * @description: Output a character to the vga. (The cursor is not modified.)
 * @param c     Character to be output.
 * @param color Output color.
 */
static void outchar(char ch)
{
    switch (ch)
    {
    case 033:
        esc();
        break;
    case '\n':
        newline();
        break;
    case '\t':
        tab();
        break;
    case '\b':
        backspace();
        break;
    case '\r':
        carriage();
        break;
    case 127:
        delete ();
        break;
    default:
        put_character(ch);
        break;
    }
}

// Clear the screen and return the cursor to its original value.
void vga_clear(void)
{
    for (int16 *p = vga.head; p < vga.tail; p++)
        *p = (int16)0x0700;
    vga.cursor = vga.head;
    reset_cursor();
}

/**
 * @description: Output a character to the vga.
 * @param ch    Character to be output.
 * @param color Output color.
 */
void vga_put_char(char ch)
{
    outchar(ch);
    scrolling();
    reset_cursor();
}

/**
 * @description: Output a string to VGA.
 * @param str   The first address of the string.
 * @param color Output color.
 */
int vga_put_chars(char *str)
{
    int num = 0;
    while (*str != '\0')
    {
        num++;
        outchar(*str++);
        scrolling();
    }
    reset_cursor();
    return num;
}

/**
 * @description: Cursor offset.
 * @param offset    Offset.
 */
void vga_offset(int offset)
{
    vga.cursor = vga.cursor + offset;
    reset_cursor();
}